关于原型污染漏洞的完整指南
编译:奇安信代码卫士
原型污染漏洞是安全社区所知不多的漏洞之一。2017年左右,研究人员开始将其视作一个潜在的攻击向量;2018年,第一批漏洞在野现身。本文将深入分析原型污染漏洞是什么以及如何缓解。
文章大纲如下:
1、原型污染:从何开始?
2、_proto_特性
3、原型污染漏洞何时产生?
4、原型污染漏洞案例
5、 原型污染漏洞增多
6、 _proto_:漏洞还是特性?
7、 缓解原型污染漏洞
8、 原型污染漏洞会绝迹吗?
原型污染:从何开始?
一切始于2015年,当时欧洲计算机制造商协会 (ECMA) 发布新标准(ECMAScript 2015,第六版),说明了 JavaScript 标准,确保不同 web 浏览器之间网页的兼容性。该标准包括了 _proto_ 特性的标准化。
_Proto_ 特性
在解释 _proto_ 是什么之前,我们先来快速查看下 JavaScript 中的一些基本定义:
Object(“对象“)可视作键值对,“键”是字符串,“值”可以是任何内容(类似于其它语言中的 “map” 或 “dictionary”)。你在 JavaScript(除了原语)中输入的所有东西就是一个object。
“prototype (原型)”是和 object 相关的一个属性,用作使 JavaScript objects 互相继承特性的机制。由于JavaScript 中几乎所有一切都是一个 object,因此 prototype 也是一个 object。
一个 Objects Prototype 可能也具有一个 “Prototype”,从中它可继承 prototype 或其它属性等,这种情况被称为“原型链”。
我们需要了解的关于 _proto_ 的三大要点是:
1、它指的是一个 object 所有 prototype 的特殊属性
2、所有 Objects 的属性 (Prototype) 都是 _proto_
3、_proto_也是一个 Objects
显然,”_proto_“意在作为特性,支持诸如从 JavaScript 类中继承所有属性等进程。但最终,”所有一切都是任意一切,而任意一切都是所有一切“有其弱点,或者更确切地说,是一个漏洞。
原型污染漏洞何时产生?
当攻击者操纵 _proto_(通常通过在 _proto_ 上新增一个新的 Prototype方式操纵)时就会发生原型污染。由于每个 object 都存在 _proto_,而且每个 Objects 都从其 Prototype 中继承 Prototypes,所有 JavaScript Objects 都会通过原型链继承该新增。恶意人员可利用这一能力将属性插入已有 JavaScript 代码中,并通过触发 JavaScript 例外执行拒绝服务攻击,或者通过插入恶意代码的方式实现远程代码执行。
以上说明的是Prototypes 的 Objects,之后再另外一个 Object(Object_#3) 的 _proto_ 之上新增了要给新的 Prototype (Prototype_#4)。这就使得所有的 Objects包括其原型(它们也都是“对象“)继承新的 Prototype即 Prototype_#4。结果就是导致应用程序崩溃。
原型污染漏洞案例:CVE-2020-28282
npm 包 “getobject” 是协助轻松获取并设置 Object’s 进程的库。该包的周下载量超过60万次且多个包有赖于它。由于 “_proto_”是JavaScript 中的一个特性,因此“getobject”等库的开发人员并非使用它才易受攻击,攻击者只需拥有一个解析 “_proto_” 的流,无需任何清理或缓解即可利用该漏洞并执行原型污染攻击。
审计 POC 代码,发现 “getobject” 包中的 set() 函数旨在将属性分配给 Object,且接受三个参数即 Obj、parts 和 value。由于不存在验证,因此,攻击者如能调整 parts 使其包含 _proto_ 提供恶意值,传递给 parts 和 value 参数的值就可遭攻击者操控。接着,check 将被分配给值为 “polluted” 的 _proto_ 即可,从而污染了应用程序中的所有 Objects。
在输出中可知,在污染发生前,obj2.check 是未定义的,之后当运行参数为 (obj, “_proto_.check”, “polluted”) 的 set() 函数时,污染发生。当前,obj2.check 存在且值为 “polluted”。通过将新的“未知” Prototype 分配给应用程序中的所有 Object,导致整个应用程序拒绝服务。
POC 代码如下:
const getObject = require('getobject');
var obj = {};
var obj2 = 1;
console.log("Before Polluting : " + obj2.Check);
getObject.set(obj, "__proto__.Check", "polluted");
console.log("After Polluting: " + obj2.Check);
输出如下:
Before Polluting : undefined
After Polluting: polluted
原型污染漏洞增多
了解了原型污染源自何处以及原型污染漏洞如何运作后,我们看一下过去几年来社区如何解决原型污染漏洞。
从上表可了解到安全社区发现原型污染漏洞后,所公开的漏洞数量。上表显示在2019年有一个很大的上升趋势,尤其在2020年更是如此。虽然现在还不知道2021年的原型污染漏洞数量到底有多少,但目前来看这种上升趋势似乎会趋于稳定。
_proto_:漏洞还是特性?
在过去,”_proto_” 可能是一个有用的特性,但由于它已标准化以确保和 web 浏览器的兼容性,该功能不再受社区推崇。目前尚不清楚该功能会被删除还是保留为兼容性服务。流行的建议是避免使用该特性:MDN 甚至弃用该特性且建议使用 Object.getPrototypeof。鉴于当前的最佳实践,很难认为 _proto_ 不是漏洞。
这种情况使保护代码不受所使用编程语言特性的危害作为维护人员和开发人员的又一项工作。
缓解原型污染漏洞
缓解原型污染漏洞的方式有很多:
Object.freeze 将缓解几乎所有情况。冻结 Object 阻止添加新的 Prototype。
使用模式验证确保 JSON 数据包含预期属性,从而删除 JSON 中出现的 _proto_。
使用映射原语。它在 EcmaScript6 标准中引入,目前在 NodeJS 环境中备受支持。
使用 Object.creat(null) 函数创建的Objects 不具有 _proto_ 属性。
总体而言,在使用递归融合函数时要特别注意,因为相比其它函数更容易受原型污染漏洞影响。
原型污染漏洞会绝迹吗?
可以肯定地说,未来还会继续检测到新的原型污染漏洞。如之前所述,目前尚不清楚未来是否或者何时将删除 _proto_。在此之前,我们在开发代码时应将该漏洞考虑在内并缓解已有案例。
原型污染 0day 漏洞影响所有流行的 Lodash 库版本(附详情和 PoC)
【漏洞预警】jQuery 前端库出现罕见的原型污染漏洞,影响范围广泛(含技术分析)
https://www.whitesourcesoftware.com/resources/blog/prototype-pollution-vulnerabilities/
题图:Pixabay License
本文由奇安信编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。
奇安信代码卫士 (codesafe)
国内首个专注于软件开发安全的产品线。